/*
 * $Id$
 *
 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package org.jdesktop.swingx.plaf.basic.core;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.Action;
import javax.swing.CellRendererPane;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListSelectionModel;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicListUI;
import javax.swing.text.Position;

import org.jdesktop.swingx.JXList;
import org.jdesktop.swingx.SwingXUtilities;
import org.jdesktop.swingx.plaf.LookAndFeelUtils;
import org.jdesktop.swingx.plaf.UIAction;
import org.jdesktop.swingx.plaf.basic.core.DragRecognitionSupport.BeforeDrag;

//import sun.swing.DefaultLookup;
//import sun.swing.SwingUtilities2;
//import sun.swing.UIAction;

/**
 * An extensible implementation of {@code ListUI} for JXList.
 * {@code BasicXListUI} instances cannot be shared between multiple lists.
 * <p>
 * 
 * The heart of added functionality is to support sorting/filtering, that is
 * keep model-selection and RowSorter state synchronized. The details are
 * delegated to a ListSortUI, but this class is responsible to manage the sortUI
 * on changes of list properties, model and view selection (same strategy as in
 * JXTable).
 * <p>
 * 
 * Note: this delegate is mostly a 1:1 copy of BasicListUI. The difference is
 * that it accesses the list elements and list elementCount exclusively through
 * the JXList api. This allows a clean implementation of sorting/filtering.
 * <p>
 * 
 * The differences (goal was to touch as little code as possible as this needs
 * to be updated on every change to core until that is changed to not access the
 * list's model directly, sigh) for core functionality:
 * <ul>
 * <li>extracted method for list.getModel().getSize (for the delegate class and
 * all contained static classes) and use that method exclusively
 * <li>similar for remaining list.getModel(): implemented wrapping listModel
 * which messages the list
 * <li>rename key for shared actionMap to keep core list actions separate (just
 * in case somebody wants both) - they point to the wrong delegate
 * <li>replaced references to SwingUtilities2 in sun packages by references to
 * c&p'ed methods in SwingXUtilities
 * <li>replaced storage of shared Input/ActionMap in defaultLookup by direct
 * storage in UIManager.
 * </ul>
 * 
 * Differences to achieve extended functionality:
 * <ul>
 * <li>added methods to un/-installSortUI and call in un/installUI(component)
 * <li>changed PropertyChangeHandler to call a) hasHandledPropertyChange to
 * allow this class to replace super handler functinality and b) updateSortUI
 * after handling all not-sorter related properties.
 * <li>changed createPropertyChangeListener to return a PropertyChangeHandler
 * <li>changed ListDataHandler to check if event handled by SortUI and delegate
 * to handler only if not
 * <li>changed createListDataListener to return a ListDataHandler
 * <li>changed ListSelectionHandler to check if event handled by SortUI and
 * delegate to handler only if not
 * </ul>
 * changed createListSelectionListener to return a ListSelectionHandler
 * 
 * Note: extension of core (instead of implement from scratch) is to keep
 * external (?) code working which expects a ui delegate of type BasicSomething.
 * LAF implementors with a custom ListUI extending BasicListUI should be able to
 * add support for JXList by adding a separate CustomXListUI extending this,
 * same as the default with parent changed. <b>Beware</b>: custom code must not
 * call access the model directly or - if they insist - convert the row index to
 * account for sorting/filtering! That's the whole point of this class.
 * 
 * @version 1.127 12/02/08
 * @author Hans Muller
 * @author Philip Milne
 * @author Shannon Hickey (drag and drop)
 */
public class BasicXListUI extends BasicListUI {
	private static final StringBuilder BASELINE_COMPONENT_KEY = new StringBuilder("List.baselineComponent");

	protected JXList list = null;
	protected CellRendererPane rendererPane;

	// Listeners that this UI attaches to the JList
	protected FocusListener focusListener;
	protected MouseInputListener mouseInputListener;
	protected ListSelectionListener listSelectionListener;
	protected ListDataListener listDataListener;
	protected PropertyChangeListener propertyChangeListener;
	private Handler handler;

	protected int[] cellHeights = null;
	protected int cellHeight = -1;
	protected int cellWidth = -1;
	protected int updateLayoutStateNeeded = modelChanged;
	/**
	 * Height of the list. When asked to paint, if the current size of the list
	 * differs, this will update the layout state.
	 */
	private int listHeight;

	/**
	 * Width of the list. When asked to paint, if the current size of the list
	 * differs, this will update the layout state.
	 */
	private int listWidth;

	/**
	 * The layout orientation of the list.
	 */
	private int layoutOrientation;

	// Following ivars are used if the list is laying out horizontally

	/**
	 * Number of columns to create.
	 */
	private int columnCount;
	/**
	 * Preferred height to make the list, this is only used if the the list is
	 * layed out horizontally.
	 */
	private int preferredHeight;
	/**
	 * Number of rows per column. This is only used if the row height is fixed.
	 */
	private int rowsPerColumn;

	/**
	 * The time factor to treate the series of typed alphanumeric key as prefix
	 * for first letter navigation.
	 */
	private long timeFactor = 1000L;

	/**
	 * Local cache of JList's client property "List.isFileList"
	 */
	private boolean isFileList = false;

	/**
	 * Local cache of JList's component orientation property
	 */
	private boolean isLeftToRight = true;

	/*
	 * The bits below define JList property changes that affect layout. When one
	 * of these properties changes we set a bit in updateLayoutStateNeeded. The
	 * change is dealt with lazily, see maybeUpdateLayoutState. Changes to the
	 * JLists model, e.g. the models length changed, are handled similarly, see
	 * DataListener.
	 */

	protected final static int modelChanged = 1 << 0;
	protected final static int selectionModelChanged = 1 << 1;
	protected final static int fontChanged = 1 << 2;
	protected final static int fixedCellWidthChanged = 1 << 3;
	protected final static int fixedCellHeightChanged = 1 << 4;
	protected final static int prototypeCellValueChanged = 1 << 5;
	protected final static int cellRendererChanged = 1 << 6;
	private final static int layoutOrientationChanged = 1 << 7;
	private final static int heightChanged = 1 << 8;
	private final static int widthChanged = 1 << 9;
	private final static int componentOrientationChanged = 1 << 10;

	private static final int DROP_LINE_THICKNESS = 2;

	// FIXME - JW LazyActionMap copy is in different package ... move here?
	public static void loadActionMap(LazyActionMap map) {
		map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN));
		map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_EXTEND));
		map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_CHANGE_LEAD));
		map.put(new Actions(Actions.SELECT_NEXT_COLUMN));
		map.put(new Actions(Actions.SELECT_NEXT_COLUMN_EXTEND));
		map.put(new Actions(Actions.SELECT_NEXT_COLUMN_CHANGE_LEAD));
		map.put(new Actions(Actions.SELECT_PREVIOUS_ROW));
		map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_EXTEND));
		map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_CHANGE_LEAD));
		map.put(new Actions(Actions.SELECT_NEXT_ROW));
		map.put(new Actions(Actions.SELECT_NEXT_ROW_EXTEND));
		map.put(new Actions(Actions.SELECT_NEXT_ROW_CHANGE_LEAD));
		map.put(new Actions(Actions.SELECT_FIRST_ROW));
		map.put(new Actions(Actions.SELECT_FIRST_ROW_EXTEND));
		map.put(new Actions(Actions.SELECT_FIRST_ROW_CHANGE_LEAD));
		map.put(new Actions(Actions.SELECT_LAST_ROW));
		map.put(new Actions(Actions.SELECT_LAST_ROW_EXTEND));
		map.put(new Actions(Actions.SELECT_LAST_ROW_CHANGE_LEAD));
		map.put(new Actions(Actions.SCROLL_UP));
		map.put(new Actions(Actions.SCROLL_UP_EXTEND));
		map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
		map.put(new Actions(Actions.SCROLL_DOWN));
		map.put(new Actions(Actions.SCROLL_DOWN_EXTEND));
		map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));
		map.put(new Actions(Actions.SELECT_ALL));
		map.put(new Actions(Actions.CLEAR_SELECTION));
		map.put(new Actions(Actions.ADD_TO_SELECTION));
		map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
		map.put(new Actions(Actions.EXTEND_TO));
		map.put(new Actions(Actions.MOVE_SELECTION_TO));

		map.put(TransferHandler.getCutAction().getValue(Action.NAME), TransferHandler.getCutAction());
		map.put(TransferHandler.getCopyAction().getValue(Action.NAME), TransferHandler.getCopyAction());
		map.put(TransferHandler.getPasteAction().getValue(Action.NAME), TransferHandler.getPasteAction());
	}

	// -------------------- X-Wrapper

	private ListModel modelX;

	private ListSortUI sortUI;

	/**
	 * Compatibility Wrapper: a synthetic model which delegates to list api and
	 * throws
	 * 
	 * @return
	 */
	protected ListModel getViewModel() {
		if (modelX == null) {
			modelX = new ListModel() {

				@Override
				public int getSize() {
					return ((JXList) list).getElementCount();
				}

				@Override
				public Object getElementAt(int index) {
					return ((JXList) list).getElementAt(index);
				}

				@Override
				public void addListDataListener(ListDataListener l) {
					throw new UnsupportedOperationException("this is a synthetic model wrapper");
				}

				@Override
				public void removeListDataListener(ListDataListener l) {
					throw new UnsupportedOperationException("this is a synthetic model wrapper");

				}

			};
		}
		return modelX;
	}

	/**
	 * @return
	 */
	protected int getElementCount() {
		return ((JXList) list).getElementCount();
	}

	protected Object getElementAt(int viewIndex) {
		return ((JXList) list).getElementAt(viewIndex);
	}

	// --------------- api to support/control sorting/filtering

	protected ListSortUI getSortUI() {
		return sortUI;
	}

	/**
	 * Installs SortUI if the list has a rowSorter. Does nothing if not.
	 */
	protected void installSortUI() {
		if (list.getRowSorter() == null)
			return;
		sortUI = new ListSortUI(list, list.getRowSorter());
	}

	/**
	 * Dispose and null's the sortUI if installed. Does nothing if not.
	 */
	protected void uninstallSortUI() {
		if (sortUI == null)
			return;
		sortUI.dispose();
		sortUI = null;
	}

	/**
	 * Called from the PropertyChangeHandler.
	 * 
	 * @param property
	 *            the name of the changed property.
	 */
	protected void updateSortUI(String property) {
		if ("rowSorter".equals(property)) {
			updateSortUIToRowSorterProperty();
		}
	}

	/**
     * 
     */
	private void updateSortUIToRowSorterProperty() {
		uninstallSortUI();
		installSortUI();
	}

	/**
	 * Returns a boolean indicating whether or not the event has been processed
	 * by the sortUI.
	 * 
	 * @param e
	 * @return
	 */
	protected boolean processedBySortUI(ListDataEvent e) {
		if (sortUI == null)
			return false;
		sortUI.modelChanged(e);
		updateLayoutStateNeeded = modelChanged;
		redrawList();
		return true;
	}

	/**
	 * Returns a boolean indicating whether or not the event has been processed
	 * by the sortUI.
	 * 
	 * @param e
	 * @return
	 */
	protected boolean processedBySortUI(ListSelectionEvent e) {
		if (sortUI == null)
			return false;
		sortUI.viewSelectionChanged(e);
		list.repaint();
		return true;
	}

	// --------------------- enhanced support
	/**
	 * Invalidates the cell size cache and revalidates/-paints the list.
	 * 
	 */
	public void invalidateCellSizeCache() {
		updateLayoutStateNeeded |= 1;
		redrawList();
	}

	// --------------------- core copy

	/**
	 * Paint one List cell: compute the relevant state, get the "rubber stamp"
	 * cell renderer component, and then use the CellRendererPane to paint it.
	 * Subclasses may want to override this method rather than paint().
	 *
	 * @see #paint
	 */
	protected void paintCell(Graphics g, int row, Rectangle rowBounds, ListCellRenderer cellRenderer, ListModel dataModel,
			ListSelectionModel selModel, int leadIndex) {
		Object value = dataModel.getElementAt(row);
		boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
		boolean isSelected = selModel.isSelectedIndex(row);

		Component rendererComponent = cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);

		int cx = rowBounds.x;
		int cy = rowBounds.y;
		int cw = rowBounds.width;
		int ch = rowBounds.height;

		if (isFileList) {
			// Shrink renderer to preferred size. This is mostly used on Windows
			// where selection is only shown around the file name, instead of
			// across the whole list cell.
			int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
			if (!isLeftToRight) {
				cx += (cw - w);
			}
			cw = w;
		}

		rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
	}

	/**
	 * Paint the rows that intersect the Graphics objects clipRect. This method
	 * calls paintCell as necessary. Subclasses may want to override these
	 * methods.
	 *
	 * @see #paintCell
	 */
	public void paint(Graphics g, JComponent c) {
		Shape clip = g.getClip();
		paintImpl(g, c);
		g.setClip(clip);

		paintDropLine(g);
	}

	private void paintImpl(Graphics g, JComponent c) {
		switch (layoutOrientation) {
			case JList.VERTICAL_WRAP:
				if (list.getHeight() != listHeight) {
					updateLayoutStateNeeded |= heightChanged;
					redrawList();
				}
				break;
			case JList.HORIZONTAL_WRAP:
				if (list.getWidth() != listWidth) {
					updateLayoutStateNeeded |= widthChanged;
					redrawList();
				}
				break;
			default:
				break;
		}
		maybeUpdateLayoutState();

		ListCellRenderer renderer = list.getCellRenderer();
		ListModel dataModel = getViewModel();
		ListSelectionModel selModel = list.getSelectionModel();
		int size;

		if ((renderer == null) || (size = dataModel.getSize()) == 0) {
			return;
		}

		// Determine how many columns we need to paint
		Rectangle paintBounds = g.getClipBounds();

		int startColumn, endColumn;
		if (c.getComponentOrientation().isLeftToRight()) {
			startColumn = convertLocationToColumn(paintBounds.x, paintBounds.y);
			endColumn = convertLocationToColumn(paintBounds.x + paintBounds.width, paintBounds.y);
		} else {
			startColumn = convertLocationToColumn(paintBounds.x + paintBounds.width, paintBounds.y);
			endColumn = convertLocationToColumn(paintBounds.x, paintBounds.y);
		}
		int maxY = paintBounds.y + paintBounds.height;
		int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
		int rowIncrement = (layoutOrientation == JList.HORIZONTAL_WRAP) ? columnCount : 1;

		for (int colCounter = startColumn; colCounter <= endColumn; colCounter++) {
			// And then how many rows in this columnn
			int row = convertLocationToRowInColumn(paintBounds.y, colCounter);
			int rowCount = getRowCount(colCounter);
			int index = getModelIndex(colCounter, row);
			Rectangle rowBounds = getCellBounds(list, index, index);

			if (rowBounds == null) {
				// Not valid, bail!
				return;
			}
			while (row < rowCount && rowBounds.y < maxY && index < size) {
				rowBounds.height = getHeight(colCounter, row);
				g.setClip(rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height);
				g.clipRect(paintBounds.x, paintBounds.y, paintBounds.width, paintBounds.height);
				paintCell(g, index, rowBounds, renderer, dataModel, selModel, leadIndex);
				rowBounds.y += rowBounds.height;
				index += rowIncrement;
				row++;
			}
		}
		// Empty out the renderer pane, allowing renderers to be gc'ed.
		rendererPane.removeAll();
	}

	private void paintDropLine(Graphics g) {
		JList.DropLocation loc = list.getDropLocation();
		if (loc == null || !loc.isInsert()) {
			return;
		}
		// PENDING JW: revisit ... side-effects?
		// Color c = DefaultLookup.getColor(list, this, "List.dropLineColor",
		// null);
		Color c = UIManager.getColor("List.dropLineColor");
		if (c != null) {
			g.setColor(c);
			Rectangle rect = getDropLineRect(loc);
			g.fillRect(rect.x, rect.y, rect.width, rect.height);
		}
	}

	private Rectangle getDropLineRect(JList.DropLocation loc) {
		int size = getElementCount();

		if (size == 0) {
			Insets insets = list.getInsets();
			if (layoutOrientation == JList.HORIZONTAL_WRAP) {
				if (isLeftToRight) {
					return new Rectangle(insets.left, insets.top, DROP_LINE_THICKNESS, 20);
				} else {
					return new Rectangle(list.getWidth() - DROP_LINE_THICKNESS - insets.right, insets.top, DROP_LINE_THICKNESS,
							20);
				}
			} else {
				return new Rectangle(insets.left, insets.top, list.getWidth() - insets.left - insets.right, DROP_LINE_THICKNESS);
			}
		}

		Rectangle rect = null;
		int index = loc.getIndex();
		boolean decr = false;

		if (layoutOrientation == JList.HORIZONTAL_WRAP) {
			if (index == size) {
				decr = true;
			} else if (index != 0 && convertModelToRow(index) != convertModelToRow(index - 1)) {

				Rectangle prev = getCellBounds(list, index - 1);
				Rectangle me = getCellBounds(list, index);
				Point p = loc.getDropPoint();

				if (isLeftToRight) {
					decr = Point2D.distance(prev.x + prev.width, prev.y + (int) (prev.height / 2.0), p.x, p.y) < Point2D
							.distance(me.x, me.y + (int) (me.height / 2.0), p.x, p.y);
				} else {
					decr = Point2D.distance(prev.x, prev.y + (int) (prev.height / 2.0), p.x, p.y) < Point2D.distance(me.x
							+ me.width, me.y + (int) (prev.height / 2.0), p.x, p.y);
				}
			}

			if (decr) {
				index--;
				rect = getCellBounds(list, index);
				if (isLeftToRight) {
					rect.x += rect.width;
				} else {
					rect.x -= DROP_LINE_THICKNESS;
				}
			} else {
				rect = getCellBounds(list, index);
				if (!isLeftToRight) {
					rect.x += rect.width - DROP_LINE_THICKNESS;
				}
			}

			if (rect.x >= list.getWidth()) {
				rect.x = list.getWidth() - DROP_LINE_THICKNESS;
			} else if (rect.x < 0) {
				rect.x = 0;
			}

			rect.width = DROP_LINE_THICKNESS;
		} else if (layoutOrientation == JList.VERTICAL_WRAP) {
			if (index == size) {
				index--;
				rect = getCellBounds(list, index);
				rect.y += rect.height;
			} else if (index != 0 && convertModelToColumn(index) != convertModelToColumn(index - 1)) {

				Rectangle prev = getCellBounds(list, index - 1);
				Rectangle me = getCellBounds(list, index);
				Point p = loc.getDropPoint();
				if (Point2D.distance(prev.x + (int) (prev.width / 2.0), prev.y + prev.height, p.x, p.y) < Point2D.distance(me.x
						+ (int) (me.width / 2.0), me.y, p.x, p.y)) {

					index--;
					rect = getCellBounds(list, index);
					rect.y += rect.height;
				} else {
					rect = getCellBounds(list, index);
				}
			} else {
				rect = getCellBounds(list, index);
			}

			if (rect.y >= list.getHeight()) {
				rect.y = list.getHeight() - DROP_LINE_THICKNESS;
			}

			rect.height = DROP_LINE_THICKNESS;
		} else {
			if (index == size) {
				index--;
				rect = getCellBounds(list, index);
				rect.y += rect.height;
			} else {
				rect = getCellBounds(list, index);
			}

			if (rect.y >= list.getHeight()) {
				rect.y = list.getHeight() - DROP_LINE_THICKNESS;
			}

			rect.height = DROP_LINE_THICKNESS;
		}

		return rect;
	}

	/**
	 * Returns the baseline.
	 *
	 * @throws NullPointerException
	 *             {@inheritDoc}
	 * @throws IllegalArgumentException
	 *             {@inheritDoc}
	 * @see javax.swing.JComponent#getBaseline(int, int)
	 * @since 1.6
	 */
	public int getBaseline(JComponent c, int width, int height) {
		super.getBaseline(c, width, height);
		int rowHeight = list.getFixedCellHeight();
		UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
		Component renderer = (Component) lafDefaults.get(BASELINE_COMPONENT_KEY);
		if (renderer == null) {
			ListCellRenderer lcr = (ListCellRenderer) UIManager.get("List.cellRenderer");

			// fix for 6711072 some LAFs like Nimbus do not provide this
			// UIManager key and we should not through a NPE here because of it
			if (lcr == null) {
				lcr = new DefaultListCellRenderer();
			}

			renderer = lcr.getListCellRendererComponent(list, "a", -1, false, false);
			lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
		}
		renderer.setFont(list.getFont());
		// JList actually has much more complex behavior here.
		// If rowHeight != -1 the rowHeight is either the max of all cell
		// heights (layout orientation != VERTICAL), or is variable depending
		// upon the cell. We assume a default size.
		// We could theoretically query the real renderer, but that would
		// not work for an empty model and the results may vary with
		// the content.
		if (rowHeight == -1) {
			rowHeight = renderer.getPreferredSize().height;
		}
		return renderer.getBaseline(Integer.MAX_VALUE, rowHeight) + list.getInsets().top;
	}

	/**
	 * Returns an enum indicating how the baseline of the component changes as
	 * the size changes.
	 *
	 * @throws NullPointerException
	 *             {@inheritDoc}
	 * @see javax.swing.JComponent#getBaseline(int, int)
	 * @since 1.6
	 */
	public Component.BaselineResizeBehavior getBaselineResizeBehavior(JComponent c) {
		super.getBaselineResizeBehavior(c);
		return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
	}

	/**
	 * The preferredSize of the list depends upon the layout orientation.
	 * <table summary="Describes the preferred size for each layout orientation">
	 * <tr>
	 * <th>Layout Orientation</th>
	 * <th>Preferred Size</th>
	 * </tr>
	 * <tr>
	 * <td>JList.VERTICAL
	 * <td>The preferredSize of the list is total height of the rows and the
	 * maximum width of the cells. If JList.fixedCellHeight is specified then
	 * the total height of the rows is just (cellVerticalMargins +
	 * fixedCellHeight) * model.getSize() where rowVerticalMargins is the space
	 * we allocate for drawing the yellow focus outline. Similarly if
	 * fixedCellWidth is specified then we just use that.</td>
	 * <tr>
	 * <td>JList.VERTICAL_WRAP
	 * <td>If the visible row count is greater than zero, the preferredHeight is
	 * the maximum cell height * visibleRowCount. If the visible row count is <=
	 * 0, the preferred height is either the current height of the list, or the
	 * maximum cell height, whichever is bigger. The preferred width is than the
	 * maximum cell width * number of columns needed. Where the number of
	 * columns needs is list.height / max cell height. Max cell height is either
	 * the fixed cell height, or is determined by iterating through all the
	 * cells to find the maximum height from the ListCellRenderer.
	 * <tr>
	 * <td>JList.HORIZONTAL_WRAP
	 * <td>If the visible row count is greater than zero, the preferredHeight is
	 * the maximum cell height * adjustedRowCount. Where visibleRowCount is used
	 * to determine the number of columns. Because this lays out horizontally
	 * the number of rows is then determined from the column count. For example,
	 * lets say you have a model with 10 items and the visible row count is 8.
	 * The number of columns needed to display this is 2, but you no longer need
	 * 8 rows to display this, you only need 5, thus the adjustedRowCount is 5.
	 * <p>
	 * If the visible row count is <= 0, the preferred height is dictated by the
	 * number of columns, which will be as many as can fit in the width of the
	 * <code>JList</code> (width / max cell width), with at least one column.
	 * The preferred height then becomes the model size / number of columns *
	 * maximum cell height. Max cell height is either the fixed cell height, or
	 * is determined by iterating through all the cells to find the maximum
	 * height from the ListCellRenderer.
	 * </table>
	 * The above specifies the raw preferred width and height. The resulting
	 * preferred width is the above width + insets.left + insets.right and the
	 * resulting preferred height is the above height + insets.top +
	 * insets.bottom. Where the <code>Insets</code> are determined from
	 * <code>list.getInsets()</code>.
	 *
	 * @param c
	 *            The JList component.
	 * @return The total size of the list.
	 */
	public Dimension getPreferredSize(JComponent c) {
		maybeUpdateLayoutState();

		int lastRow = getElementCount() - 1;
		if (lastRow < 0) {
			return new Dimension(0, 0);
		}

		Insets insets = list.getInsets();
		int width = cellWidth * columnCount + insets.left + insets.right;
		int height;

		if (layoutOrientation != JList.VERTICAL) {
			height = preferredHeight;
		} else {
			Rectangle bounds = getCellBounds(list, lastRow);

			if (bounds != null) {
				height = bounds.y + bounds.height + insets.bottom;
			} else {
				height = 0;
			}
		}
		return new Dimension(width, height);
	}

	/**
	 * Selected the previous row and force it to be visible.
	 *
	 * @see JList#ensureIndexIsVisible
	 */
	protected void selectPreviousIndex() {
		int s = list.getSelectedIndex();
		if (s > 0) {
			s -= 1;
			list.setSelectedIndex(s);
			list.ensureIndexIsVisible(s);
		}
	}

	/**
	 * Selected the previous row and force it to be visible.
	 *
	 * @see JList#ensureIndexIsVisible
	 */
	protected void selectNextIndex() {
		int s = list.getSelectedIndex();
		if ((s + 1) < getElementCount()) {
			s += 1;
			list.setSelectedIndex(s);
			list.ensureIndexIsVisible(s);
		}
	}

	/**
	 * Registers the keyboard bindings on the <code>JList</code> that the
	 * <code>BasicXListUI</code> is associated with. This method is called at
	 * installUI() time.
	 *
	 * @see #installUI
	 */
	protected void installKeyboardActions() {
		InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);

		SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, inputMap);

		LazyActionMap.installLazyActionMap(list, BasicXListUI.class, "XList.actionMap");
	}

	InputMap getInputMap(int condition) {
		if (condition == JComponent.WHEN_FOCUSED) {
			// PENDING JW: side-effect when reverting to ui manager? revisit!
			InputMap keyMap = (InputMap) UIManager.get("List.focusInputMap");
			// InputMap keyMap = (InputMap)DefaultLookup.get(
			// list, this, "List.focusInputMap");
			InputMap rtlKeyMap;

			if (isLeftToRight || ((rtlKeyMap = (InputMap) UIManager.get("List.focusInputMap.RightToLeft")) == null)) {
				// ((rtlKeyMap = (InputMap)DefaultLookup.get(list, this,
				// "List.focusInputMap.RightToLeft")) == null)) {
				return keyMap;
			} else {
				rtlKeyMap.setParent(keyMap);
				return rtlKeyMap;
			}
		}
		return null;
	}

	/**
	 * Unregisters keyboard actions installed from
	 * <code>installKeyboardActions</code>. This method is called at
	 * uninstallUI() time - subclassess should ensure that all of the keyboard
	 * actions registered at installUI time are removed here.
	 *
	 * @see #installUI
	 */
	protected void uninstallKeyboardActions() {
		SwingUtilities.replaceUIActionMap(list, null);
		SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null);
	}

	/**
	 * Create and install the listeners for the JList, its model, and its
	 * selectionModel. This method is called at installUI() time.
	 *
	 * @see #installUI
	 * @see #uninstallListeners
	 */
	protected void installListeners() {
		TransferHandler th = list.getTransferHandler();
		if (th == null || th instanceof UIResource) {
			list.setTransferHandler(defaultTransferHandler);
			// default TransferHandler doesn't support drop
			// so we don't want drop handling
			if (list.getDropTarget() instanceof UIResource) {
				list.setDropTarget(null);
			}
		}

		focusListener = createFocusListener();
		mouseInputListener = createMouseInputListener();
		propertyChangeListener = createPropertyChangeListener();
		listSelectionListener = createListSelectionListener();
		listDataListener = createListDataListener();

		list.addFocusListener(focusListener);
		list.addMouseListener(mouseInputListener);
		list.addMouseMotionListener(mouseInputListener);
		list.addPropertyChangeListener(propertyChangeListener);
		list.addKeyListener(getHandler());
		// JW: here we really want the model
		ListModel model = list.getModel();
		if (model != null) {
			model.addListDataListener(listDataListener);
		}

		ListSelectionModel selectionModel = list.getSelectionModel();
		if (selectionModel != null) {
			selectionModel.addListSelectionListener(listSelectionListener);
		}
	}

	/**
	 * Remove the listeners for the JList, its model, and its selectionModel.
	 * All of the listener fields, are reset to null here. This method is called
	 * at uninstallUI() time, it should be kept in sync with installListeners.
	 *
	 * @see #uninstallUI
	 * @see #installListeners
	 */
	protected void uninstallListeners() {
		list.removeFocusListener(focusListener);
		list.removeMouseListener(mouseInputListener);
		list.removeMouseMotionListener(mouseInputListener);
		list.removePropertyChangeListener(propertyChangeListener);
		list.removeKeyListener(getHandler());

		ListModel model = list.getModel();
		if (model != null) {
			model.removeListDataListener(listDataListener);
		}

		ListSelectionModel selectionModel = list.getSelectionModel();
		if (selectionModel != null) {
			selectionModel.removeListSelectionListener(listSelectionListener);
		}

		focusListener = null;
		mouseInputListener = null;
		listSelectionListener = null;
		listDataListener = null;
		propertyChangeListener = null;
		handler = null;
	}

	/**
	 * Initialize JList properties, e.g. font, foreground, and background, and
	 * add the CellRendererPane. The font, foreground, and background properties
	 * are only set if their current value is either null or a UIResource, other
	 * properties are set if the current value is null.
	 *
	 * @see #uninstallDefaults
	 * @see #installUI
	 * @see CellRendererPane
	 */
	protected void installDefaults() {
		list.setLayout(null);

		LookAndFeel.installBorder(list, "List.border");

		LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");

		LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);

		if (list.getCellRenderer() == null) {
			list.setCellRenderer((ListCellRenderer) (UIManager.get("List.cellRenderer")));
		}

		Color sbg = list.getSelectionBackground();
		if (sbg == null || sbg instanceof UIResource) {
			list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
		}

		Color sfg = list.getSelectionForeground();
		if (sfg == null || sfg instanceof UIResource) {
			list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
		}

		Long l = (Long) UIManager.get("List.timeFactor");
		timeFactor = (l != null) ? l.longValue() : 1000L;

		updateIsFileList();
	}

	private void updateIsFileList() {
		boolean b = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
		if (b != isFileList) {
			isFileList = b;
			Font oldFont = list.getFont();
			if (oldFont == null || oldFont instanceof UIResource) {
				Font newFont = UIManager.getFont(b ? "FileChooser.listFont" : "List.font");
				if (newFont != null && newFont != oldFont) {
					list.setFont(newFont);
				}
			}
		}
	}

	/**
	 * Set the JList properties that haven't been explicitly overridden to null.
	 * A property is considered overridden if its current value is not a
	 * UIResource.
	 *
	 * @see #installDefaults
	 * @see #uninstallUI
	 * @see CellRendererPane
	 */
	protected void uninstallDefaults() {
		LookAndFeel.uninstallBorder(list);
		if (list.getFont() instanceof UIResource) {
			list.setFont(null);
		}
		if (list.getForeground() instanceof UIResource) {
			list.setForeground(null);
		}
		if (list.getBackground() instanceof UIResource) {
			list.setBackground(null);
		}
		if (list.getSelectionBackground() instanceof UIResource) {
			list.setSelectionBackground(null);
		}
		if (list.getSelectionForeground() instanceof UIResource) {
			list.setSelectionForeground(null);
		}
		if (list.getCellRenderer() instanceof UIResource) {
			list.setCellRenderer(null);
		}
		if (list.getTransferHandler() instanceof UIResource) {
			list.setTransferHandler(null);
		}
	}

	/**
	 * Initializes <code>this.list</code> by calling
	 * <code>installDefaults()</code>, <code>installListeners()</code>, and
	 * <code>installKeyboardActions()</code> in order.
	 *
	 * @see #installDefaults
	 * @see #installListeners
	 * @see #installKeyboardActions
	 */
	public void installUI(JComponent c) {
		list = (JXList) c;

		layoutOrientation = list.getLayoutOrientation();

		rendererPane = new CellRendererPane();
		list.add(rendererPane);

		columnCount = 1;

		updateLayoutStateNeeded = modelChanged;
		isLeftToRight = list.getComponentOrientation().isLeftToRight();

		installDefaults();
		installListeners();
		installKeyboardActions();
		installSortUI();
	}

	/**
	 * Uninitializes <code>this.list</code> by calling
	 * <code>uninstallListeners()</code>,
	 * <code>uninstallKeyboardActions()</code>, and
	 * <code>uninstallDefaults()</code> in order. Sets this.list to null.
	 *
	 * @see #uninstallListeners
	 * @see #uninstallKeyboardActions
	 * @see #uninstallDefaults
	 */
	public void uninstallUI(JComponent c) {
		uninstallSortUI();
		uninstallListeners();
		uninstallDefaults();
		uninstallKeyboardActions();

		cellWidth = cellHeight = -1;
		cellHeights = null;

		listWidth = listHeight = -1;

		list.remove(rendererPane);
		rendererPane = null;
		list = null;
	}

	/**
	 * Returns a new instance of BasicXListUI. BasicXListUI delegates are
	 * allocated one per JList.
	 *
	 * @return A new ListUI implementation for the Windows look and feel.
	 */
	public static ComponentUI createUI(JComponent list) {
		return new BasicXListUI();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @throws NullPointerException
	 *             {@inheritDoc}
	 */
	public int locationToIndex(JList list, Point location) {
		maybeUpdateLayoutState();
		return convertLocationToModel(location.x, location.y);
	}

	/**
	 * {@inheritDoc}
	 */
	public Point indexToLocation(JList list, int index) {
		maybeUpdateLayoutState();
		Rectangle rect = getCellBounds(list, index, index);

		if (rect != null) {
			return new Point(rect.x, rect.y);
		}
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	public Rectangle getCellBounds(JList list, int index1, int index2) {
		maybeUpdateLayoutState();

		int minIndex = Math.min(index1, index2);
		int maxIndex = Math.max(index1, index2);

		if (minIndex >= getElementCount()) {
			return null;
		}

		Rectangle minBounds = getCellBounds(list, minIndex);

		if (minBounds == null) {
			return null;
		}
		if (minIndex == maxIndex) {
			return minBounds;
		}
		Rectangle maxBounds = getCellBounds(list, maxIndex);

		if (maxBounds != null) {
			if (layoutOrientation == JList.HORIZONTAL_WRAP) {
				int minRow = convertModelToRow(minIndex);
				int maxRow = convertModelToRow(maxIndex);

				if (minRow != maxRow) {
					minBounds.x = 0;
					minBounds.width = list.getWidth();
				}
			} else if (minBounds.x != maxBounds.x) {
				// Different columns
				minBounds.y = 0;
				minBounds.height = list.getHeight();
			}
			minBounds.add(maxBounds);
		}
		return minBounds;
	}

	/**
	 * Gets the bounds of the specified model index, returning the resulting
	 * bounds, or null if <code>index</code> is not valid.
	 */
	private Rectangle getCellBounds(JList list, int index) {
		maybeUpdateLayoutState();

		int row = convertModelToRow(index);
		int column = convertModelToColumn(index);

		if (row == -1 || column == -1) {
			return null;
		}

		Insets insets = list.getInsets();
		int x;
		int w = cellWidth;
		int y = insets.top;
		int h;
		switch (layoutOrientation) {
			case JList.VERTICAL_WRAP:
			case JList.HORIZONTAL_WRAP:
				if (isLeftToRight) {
					x = insets.left + column * cellWidth;
				} else {
					x = list.getWidth() - insets.right - (column + 1) * cellWidth;
				}
				y += cellHeight * row;
				h = cellHeight;
				break;
			default:
				x = insets.left;
				if (cellHeights == null) {
					y += (cellHeight * row);
				} else if (row >= cellHeights.length) {
					y = 0;
				} else {
					for (int i = 0; i < row; i++) {
						y += cellHeights[i];
					}
				}
				w = list.getWidth() - (insets.left + insets.right);
				h = getRowHeight(index);
				break;
		}
		return new Rectangle(x, y, w, h);
	}

	/**
	 * Returns the height of the specified row based on the current layout.
	 *
	 * @return The specified row height or -1 if row isn't valid.
	 * @see #convertYToRow
	 * @see #convertRowToY
	 * @see #updateLayoutState
	 */
	protected int getRowHeight(int row) {
		return getHeight(0, row);
	}

	/**
	 * Convert the JList relative coordinate to the row that contains it, based
	 * on the current layout. If y0 doesn't fall within any row, return -1.
	 *
	 * @return The row that contains y0, or -1.
	 * @see #getRowHeight
	 * @see #updateLayoutState
	 */
	protected int convertYToRow(int y0) {
		return convertLocationToRow(0, y0, false);
	}

	/**
	 * Return the JList relative Y coordinate of the origin of the specified row
	 * or -1 if row isn't valid.
	 *
	 * @return The Y coordinate of the origin of row, or -1.
	 * @see #getRowHeight
	 * @see #updateLayoutState
	 */
	protected int convertRowToY(int row) {
		if (row >= getRowCount(0) || row < 0) {
			return -1;
		}
		Rectangle bounds = getCellBounds(list, row, row);
		return bounds.y;
	}

	/**
	 * Returns the height of the cell at the passed in location.
	 */
	private int getHeight(int column, int row) {
		if (column < 0 || column > columnCount || row < 0) {
			return -1;
		}
		if (layoutOrientation != JList.VERTICAL) {
			return cellHeight;
		}
		if (row >= getElementCount()) {
			return -1;
		}
		return (cellHeights == null) ? cellHeight : ((row < cellHeights.length) ? cellHeights[row] : -1);
	}

	/**
	 * Returns the row at location x/y.
	 *
	 * @param closest
	 *            If true and the location doesn't exactly match a particular
	 *            location, this will return the closest row.
	 */
	private int convertLocationToRow(int x, int y0, boolean closest) {
		int size = getElementCount();

		if (size <= 0) {
			return -1;
		}
		Insets insets = list.getInsets();
		if (cellHeights == null) {
			int row = (cellHeight == 0) ? 0 : ((y0 - insets.top) / cellHeight);
			if (closest) {
				if (row < 0) {
					row = 0;
				} else if (row >= size) {
					row = size - 1;
				}
			}
			return row;
		} else if (size > cellHeights.length) {
			return -1;
		} else {
			int y = insets.top;
			int row = 0;

			if (closest && y0 < y) {
				return 0;
			}
			int i;
			for (i = 0; i < size; i++) {
				if ((y0 >= y) && (y0 < y + cellHeights[i])) {
					return row;
				}
				y += cellHeights[i];
				row += 1;
			}
			return i - 1;
		}
	}

	/**
	 * Returns the closest row that starts at the specified y-location in the
	 * passed in column.
	 */
	private int convertLocationToRowInColumn(int y, int column) {
		int x = 0;

		if (layoutOrientation != JList.VERTICAL) {
			if (isLeftToRight) {
				x = column * cellWidth;
			} else {
				x = list.getWidth() - (column + 1) * cellWidth - list.getInsets().right;
			}
		}
		return convertLocationToRow(x, y, true);
	}

	/**
	 * Returns the closest location to the model index of the passed in
	 * location.
	 */
	private int convertLocationToModel(int x, int y) {
		int row = convertLocationToRow(x, y, true);
		int column = convertLocationToColumn(x, y);

		if (row >= 0 && column >= 0) {
			return getModelIndex(column, row);
		}
		return -1;
	}

	/**
	 * Returns the number of rows in the given column.
	 */
	private int getRowCount(int column) {
		if (column < 0 || column >= columnCount) {
			return -1;
		}
		if (layoutOrientation == JList.VERTICAL || (column == 0 && columnCount == 1)) {
			return getElementCount();
		}
		if (column >= columnCount) {
			return -1;
		}
		if (layoutOrientation == JList.VERTICAL_WRAP) {
			if (column < (columnCount - 1)) {
				return rowsPerColumn;
			}
			return getElementCount() - (columnCount - 1) * rowsPerColumn;
		}
		// JList.HORIZONTAL_WRAP
		int diff = columnCount - (columnCount * rowsPerColumn - getElementCount());

		if (column >= diff) {
			return Math.max(0, rowsPerColumn - 1);
		}
		return rowsPerColumn;
	}

	/**
	 * Returns the model index for the specified display location. If
	 * <code>column</code>x<code>row</code> is beyond the length of the model,
	 * this will return the model size - 1.
	 */
	private int getModelIndex(int column, int row) {
		switch (layoutOrientation) {
			case JList.VERTICAL_WRAP:
				return Math.min(getElementCount() - 1, rowsPerColumn * column + Math.min(row, rowsPerColumn - 1));
			case JList.HORIZONTAL_WRAP:
				return Math.min(getElementCount() - 1, row * columnCount + column);
			default:
				return row;
		}
	}

	/**
	 * Returns the closest column to the passed in location.
	 */
	private int convertLocationToColumn(int x, int y) {
		if (cellWidth > 0) {
			if (layoutOrientation == JList.VERTICAL) {
				return 0;
			}
			Insets insets = list.getInsets();
			int col;
			if (isLeftToRight) {
				col = (x - insets.left) / cellWidth;
			} else {
				col = (list.getWidth() - x - insets.right - 1) / cellWidth;
			}
			if (col < 0) {
				return 0;
			} else if (col >= columnCount) {
				return columnCount - 1;
			}
			return col;
		}
		return 0;
	}

	/**
	 * Returns the row that the model index <code>index</code> will be displayed
	 * in..
	 */
	private int convertModelToRow(int index) {
		int size = getElementCount();

		if ((index < 0) || (index >= size)) {
			return -1;
		}

		if (layoutOrientation != JList.VERTICAL && columnCount > 1 && rowsPerColumn > 0) {
			if (layoutOrientation == JList.VERTICAL_WRAP) {
				return index % rowsPerColumn;
			}
			return index / columnCount;
		}
		return index;
	}

	/**
	 * Returns the column that the model index <code>index</code> will be
	 * displayed in.
	 */
	private int convertModelToColumn(int index) {
		int size = getElementCount();

		if ((index < 0) || (index >= size)) {
			return -1;
		}

		if (layoutOrientation != JList.VERTICAL && rowsPerColumn > 0 && columnCount > 1) {
			if (layoutOrientation == JList.VERTICAL_WRAP) {
				return index / rowsPerColumn;
			}
			return index % columnCount;
		}
		return 0;
	}

	/**
	 * If updateLayoutStateNeeded is non zero, call updateLayoutState() and
	 * reset updateLayoutStateNeeded. This method should be called by methods
	 * before doing any computation based on the geometry of the list. For
	 * example it's the first call in paint() and getPreferredSize().
	 *
	 * @see #updateLayoutState
	 */
	protected void maybeUpdateLayoutState() {
		if (updateLayoutStateNeeded != 0) {
			updateLayoutState();
			updateLayoutStateNeeded = 0;
		}
	}

	/**
	 * Recompute the value of cellHeight or cellHeights based and cellWidth,
	 * based on the current font and the current values of fixedCellWidth,
	 * fixedCellHeight, and prototypeCellValue.
	 *
	 * @see #maybeUpdateLayoutState
	 */
	protected void updateLayoutState() {
		/*
		 * If both JList fixedCellWidth and fixedCellHeight have been set, then
		 * initialize cellWidth and cellHeight, and set cellHeights to null.
		 */

		int fixedCellHeight = list.getFixedCellHeight();
		int fixedCellWidth = list.getFixedCellWidth();

		cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;

		if (fixedCellHeight != -1) {
			cellHeight = fixedCellHeight;
			cellHeights = null;
		} else {
			cellHeight = -1;
			cellHeights = new int[getElementCount()];
		}

		/*
		 * If either of JList fixedCellWidth and fixedCellHeight haven't been
		 * set, then initialize cellWidth and cellHeights by scanning through
		 * the entire model. Note: if the renderer is null, we just set
		 * cellWidth and cellHeights[*] to zero, if they're not set already.
		 */

		if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {

			ListModel dataModel = getViewModel();
			int dataModelSize = dataModel.getSize();
			ListCellRenderer renderer = list.getCellRenderer();

			if (renderer != null) {
				for (int index = 0; index < dataModelSize; index++) {
					Object value = dataModel.getElementAt(index);
					Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
					rendererPane.add(c);
					Dimension cellSize = c.getPreferredSize();
					if (fixedCellWidth == -1) {
						cellWidth = Math.max(cellSize.width, cellWidth);
					}
					if (fixedCellHeight == -1) {
						cellHeights[index] = cellSize.height;
					}
				}
			} else {
				if (cellWidth == -1) {
					cellWidth = 0;
				}
				if (cellHeights == null) {
					cellHeights = new int[dataModelSize];
				}
				for (int index = 0; index < dataModelSize; index++) {
					cellHeights[index] = 0;
				}
			}
		}

		columnCount = 1;
		if (layoutOrientation != JList.VERTICAL) {
			updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
		}
	}

	/**
	 * Invoked when the list is layed out horizontally to determine how many
	 * columns to create.
	 * <p>
	 * This updates the <code>rowsPerColumn, </code><code>columnCount</code>,
	 * <code>preferredHeight</code> and potentially <code>cellHeight</code>
	 * instance variables.
	 */
	private void updateHorizontalLayoutState(int fixedCellWidth, int fixedCellHeight) {
		int visRows = list.getVisibleRowCount();
		int dataModelSize = getElementCount();
		Insets insets = list.getInsets();

		listHeight = list.getHeight();
		listWidth = list.getWidth();

		if (dataModelSize == 0) {
			rowsPerColumn = columnCount = 0;
			preferredHeight = insets.top + insets.bottom;
			return;
		}

		int height;

		if (fixedCellHeight != -1) {
			height = fixedCellHeight;
		} else {
			// Determine the max of the renderer heights.
			int maxHeight = 0;
			if (cellHeights.length > 0) {
				maxHeight = cellHeights[cellHeights.length - 1];
				for (int counter = cellHeights.length - 2; counter >= 0; counter--) {
					maxHeight = Math.max(maxHeight, cellHeights[counter]);
				}
			}
			height = cellHeight = maxHeight;
			cellHeights = null;
		}
		// The number of rows is either determined by the visible row
		// count, or by the height of the list.
		rowsPerColumn = dataModelSize;
		if (visRows > 0) {
			rowsPerColumn = visRows;
			columnCount = Math.max(1, dataModelSize / rowsPerColumn);
			if (dataModelSize > 0 && dataModelSize > rowsPerColumn && dataModelSize % rowsPerColumn != 0) {
				columnCount++;
			}
			if (layoutOrientation == JList.HORIZONTAL_WRAP) {
				// Because HORIZONTAL_WRAP flows differently, the
				// rowsPerColumn needs to be adjusted.
				rowsPerColumn = (dataModelSize / columnCount);
				if (dataModelSize % columnCount > 0) {
					rowsPerColumn++;
				}
			}
		} else if (layoutOrientation == JList.VERTICAL_WRAP && height != 0) {
			rowsPerColumn = Math.max(1, (listHeight - insets.top - insets.bottom) / height);
			columnCount = Math.max(1, dataModelSize / rowsPerColumn);
			if (dataModelSize > 0 && dataModelSize > rowsPerColumn && dataModelSize % rowsPerColumn != 0) {
				columnCount++;
			}
		} else if (layoutOrientation == JList.HORIZONTAL_WRAP && cellWidth > 0 && listWidth > 0) {
			columnCount = Math.max(1, (listWidth - insets.left - insets.right) / cellWidth);
			rowsPerColumn = dataModelSize / columnCount;
			if (dataModelSize % columnCount > 0) {
				rowsPerColumn++;
			}
		}
		preferredHeight = rowsPerColumn * cellHeight + insets.top + insets.bottom;
	}

	private Handler getHandler() {
		if (handler == null) {
			handler = new Handler();
		}
		return handler;
	}

	/**
	 * Mouse input, and focus handling for JList. An instance of this class is
	 * added to the appropriate java.awt.Component lists at installUI() time.
	 * Note keyboard input is handled with JComponent KeyboardActions, see
	 * installKeyboardActions().
	 * <p>
	 * <strong>Warning:</strong> Serialized objects of this class will not be
	 * compatible with future Swing releases. The current serialization support
	 * is appropriate for short term storage or RMI between applications running
	 * the same version of Swing. As of 1.4, support for long term storage of
	 * all JavaBeans<sup><font size="-2">TM</font></sup> has been added to the
	 * <code>java.beans</code> package. Please see {@link java.beans.XMLEncoder}.
	 *
	 * @see #createMouseInputListener
	 * @see #installKeyboardActions
	 * @see #installUI
	 */
	public class MouseInputHandler implements MouseInputListener {
		public void mouseClicked(MouseEvent e) {
			getHandler().mouseClicked(e);
		}

		public void mouseEntered(MouseEvent e) {
			getHandler().mouseEntered(e);
		}

		public void mouseExited(MouseEvent e) {
			getHandler().mouseExited(e);
		}

		public void mousePressed(MouseEvent e) {
			getHandler().mousePressed(e);
		}

		public void mouseDragged(MouseEvent e) {
			getHandler().mouseDragged(e);
		}

		public void mouseMoved(MouseEvent e) {
			getHandler().mouseMoved(e);
		}

		public void mouseReleased(MouseEvent e) {
			getHandler().mouseReleased(e);
		}
	}

	/**
	 * Creates a delegate that implements MouseInputListener. The delegate is
	 * added to the corresponding java.awt.Component listener lists at
	 * installUI() time. Subclasses can override this method to return a custom
	 * MouseInputListener, e.g.
	 * 
	 * <pre>
	 * class MyListUI extends BasicXListUI {
	 *    protected MouseInputListener <b>createMouseInputListener</b>() {
	 *        return new MyMouseInputHandler();
	 *    }
	 *    public class MyMouseInputHandler extends MouseInputHandler {
	 *        public void mouseMoved(MouseEvent e) {
	 *            // do some extra work when the mouse moves
	 *            super.mouseMoved(e);
	 *        }
	 *    }
	 * }
	 * </pre>
	 *
	 * @see MouseInputHandler
	 * @see #installUI
	 */
	protected MouseInputListener createMouseInputListener() {
		return getHandler();
	}

	/**
	 * This inner class is marked &quot;public&quot; due to a compiler bug. This
	 * class should be treated as a &quot;protected&quot; inner class.
	 * Instantiate it only within subclasses of BasicTableUI.
	 */
	public class FocusHandler implements FocusListener {
		protected void repaintCellFocus() {
			getHandler().repaintCellFocus();
		}

		/*
		 * The focusGained() focusLost() methods run when the JList focus
		 * changes.
		 */

		public void focusGained(FocusEvent e) {
			getHandler().focusGained(e);
		}

		public void focusLost(FocusEvent e) {
			getHandler().focusLost(e);
		}
	}

	protected FocusListener createFocusListener() {
		return getHandler();
	}

	/**
	 * The ListSelectionListener that's added to the JLists selection model at
	 * installUI time, and whenever the JList.selectionModel property changes.
	 * When the selection changes we repaint the affected rows.
	 * <p>
	 * <strong>Warning:</strong> Serialized objects of this class will not be
	 * compatible with future Swing releases. The current serialization support
	 * is appropriate for short term storage or RMI between applications running
	 * the same version of Swing. As of 1.4, support for long term storage of
	 * all JavaBeans<sup><font size="-2">TM</font></sup> has been added to the
	 * <code>java.beans</code> package. Please see {@link java.beans.XMLEncoder}.
	 *
	 * @see #createListSelectionListener
	 * @see #getCellBounds
	 * @see #installUI
	 */
	public class ListSelectionHandler implements ListSelectionListener {
		public void valueChanged(ListSelectionEvent e) {
			if (processedBySortUI(e))
				return;
			getHandler().valueChanged(e);
		}
	}

	/**
	 * Creates an instance of ListSelectionHandler that's added to the JLists by
	 * selectionModel as needed. Subclasses can override this method to return a
	 * custom ListSelectionListener, e.g.
	 * 
	 * <pre>
	 * class MyListUI extends BasicXListUI {
	 *    protected ListSelectionListener <b>createListSelectionListener</b>() {
	 *        return new MySelectionListener();
	 *    }
	 *    public class MySelectionListener extends ListSelectionHandler {
	 *        public void valueChanged(ListSelectionEvent e) {
	 *            // do some extra work when the selection changes
	 *            super.valueChange(e);
	 *        }
	 *    }
	 * }
	 * </pre>
	 *
	 * @see ListSelectionHandler
	 * @see #installUI
	 */
	protected ListSelectionListener createListSelectionListener() {
		return new ListSelectionHandler();
	}

	private void redrawList() {
		list.revalidate();
		list.repaint();
	}

	/**
	 * The ListDataListener that's added to the JLists model at installUI time,
	 * and whenever the JList.model property changes.
	 * <p>
	 * <strong>Warning:</strong> Serialized objects of this class will not be
	 * compatible with future Swing releases. The current serialization support
	 * is appropriate for short term storage or RMI between applications running
	 * the same version of Swing. As of 1.4, support for long term storage of
	 * all JavaBeans<sup><font size="-2">TM</font></sup> has been added to the
	 * <code>java.beans</code> package. Please see {@link java.beans.XMLEncoder}.
	 *
	 * @see JList#getModel
	 * @see #maybeUpdateLayoutState
	 * @see #createListDataListener
	 * @see #installUI
	 */
	public class ListDataHandler implements ListDataListener {
		public void intervalAdded(ListDataEvent e) {
			if (processedBySortUI(e))
				return;
			getHandler().intervalAdded(e);
		}

		public void intervalRemoved(ListDataEvent e) {
			if (processedBySortUI(e))
				return;
			getHandler().intervalRemoved(e);
		}

		public void contentsChanged(ListDataEvent e) {
			if (processedBySortUI(e))
				return;
			getHandler().contentsChanged(e);
		}
	}

	/**
	 * Creates an instance of ListDataListener that's added to the JLists by
	 * model as needed. Subclasses can override this method to return a custom
	 * ListDataListener, e.g.
	 * 
	 * <pre>
	 * class MyListUI extends BasicXListUI {
	 *    protected ListDataListener <b>createListDataListener</b>() {
	 *        return new MyListDataListener();
	 *    }
	 *    public class MyListDataListener extends ListDataHandler {
	 *        public void contentsChanged(ListDataEvent e) {
	 *            // do some extra work when the models contents change
	 *            super.contentsChange(e);
	 *        }
	 *    }
	 * }
	 * </pre>
	 *
	 * @see ListDataListener
	 * @see JList#getModel
	 * @see #installUI
	 */
	protected ListDataListener createListDataListener() {
		return new ListDataHandler();
	}

	/**
	 * The PropertyChangeListener that's added to the JList at installUI time.
	 * When the value of a JList property that affects layout changes, we set a
	 * bit in updateLayoutStateNeeded. If the JLists model changes we
	 * additionally remove our listeners from the old model. Likewise for the
	 * JList selectionModel.
	 * <p>
	 * <strong>Warning:</strong> Serialized objects of this class will not be
	 * compatible with future Swing releases. The current serialization support
	 * is appropriate for short term storage or RMI between applications running
	 * the same version of Swing. As of 1.4, support for long term storage of
	 * all JavaBeans<sup><font size="-2">TM</font></sup> has been added to the
	 * <code>java.beans</code> package. Please see {@link java.beans.XMLEncoder}.
	 *
	 * @see #maybeUpdateLayoutState
	 * @see #createPropertyChangeListener
	 * @see #installUI
	 */
	public class PropertyChangeHandler implements PropertyChangeListener {
		public void propertyChange(PropertyChangeEvent e) {
			getHandler().propertyChange(e);
			updateSortUI(e.getPropertyName());
		}
	}

	/**
	 * Creates an instance of PropertyChangeHandler that's added to the JList by
	 * installUI(). Subclasses can override this method to return a custom
	 * PropertyChangeListener, e.g.
	 * 
	 * <pre>
	 * class MyListUI extends BasicXListUI {
	 *    protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
	 *        return new MyPropertyChangeListener();
	 *    }
	 *    public class MyPropertyChangeListener extends PropertyChangeHandler {
	 *        public void propertyChange(PropertyChangeEvent e) {
	 *            if (e.getPropertyName().equals("model")) {
	 *                // do some extra work when the model changes
	 *            }
	 *            super.propertyChange(e);
	 *        }
	 *    }
	 * }
	 * </pre>
	 *
	 * @see PropertyChangeListener
	 * @see #installUI
	 */
	protected PropertyChangeListener createPropertyChangeListener() {
		return new PropertyChangeHandler();
	}

	/**
	 * Used by IncrementLeadSelectionAction. Indicates the action should change
	 * the lead, and not select it.
	 */
	private static final int CHANGE_LEAD = 0;
	/**
	 * Used by IncrementLeadSelectionAction. Indicates the action should change
	 * the selection and lead.
	 */
	private static final int CHANGE_SELECTION = 1;
	/**
	 * Used by IncrementLeadSelectionAction. Indicates the action should extend
	 * the selection from the anchor to the next index.
	 */
	private static final int EXTEND_SELECTION = 2;

	// PENDING JW: this is not a complete replacement of sun.UIAction ...
	private static class Actions extends UIAction {
		private static final String SELECT_PREVIOUS_COLUMN = "selectPreviousColumn";
		private static final String SELECT_PREVIOUS_COLUMN_EXTEND = "selectPreviousColumnExtendSelection";
		private static final String SELECT_PREVIOUS_COLUMN_CHANGE_LEAD = "selectPreviousColumnChangeLead";
		private static final String SELECT_NEXT_COLUMN = "selectNextColumn";
		private static final String SELECT_NEXT_COLUMN_EXTEND = "selectNextColumnExtendSelection";
		private static final String SELECT_NEXT_COLUMN_CHANGE_LEAD = "selectNextColumnChangeLead";
		private static final String SELECT_PREVIOUS_ROW = "selectPreviousRow";
		private static final String SELECT_PREVIOUS_ROW_EXTEND = "selectPreviousRowExtendSelection";
		private static final String SELECT_PREVIOUS_ROW_CHANGE_LEAD = "selectPreviousRowChangeLead";
		private static final String SELECT_NEXT_ROW = "selectNextRow";
		private static final String SELECT_NEXT_ROW_EXTEND = "selectNextRowExtendSelection";
		private static final String SELECT_NEXT_ROW_CHANGE_LEAD = "selectNextRowChangeLead";
		private static final String SELECT_FIRST_ROW = "selectFirstRow";
		private static final String SELECT_FIRST_ROW_EXTEND = "selectFirstRowExtendSelection";
		private static final String SELECT_FIRST_ROW_CHANGE_LEAD = "selectFirstRowChangeLead";
		private static final String SELECT_LAST_ROW = "selectLastRow";
		private static final String SELECT_LAST_ROW_EXTEND = "selectLastRowExtendSelection";
		private static final String SELECT_LAST_ROW_CHANGE_LEAD = "selectLastRowChangeLead";
		private static final String SCROLL_UP = "scrollUp";
		private static final String SCROLL_UP_EXTEND = "scrollUpExtendSelection";
		private static final String SCROLL_UP_CHANGE_LEAD = "scrollUpChangeLead";
		private static final String SCROLL_DOWN = "scrollDown";
		private static final String SCROLL_DOWN_EXTEND = "scrollDownExtendSelection";
		private static final String SCROLL_DOWN_CHANGE_LEAD = "scrollDownChangeLead";
		private static final String SELECT_ALL = "selectAll";
		private static final String CLEAR_SELECTION = "clearSelection";

		// add the lead item to the selection without changing lead or anchor
		private static final String ADD_TO_SELECTION = "addToSelection";

		// toggle the selected state of the lead item and move the anchor to it
		private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";

		// extend the selection to the lead item
		private static final String EXTEND_TO = "extendTo";

		// move the anchor to the lead and ensure only that item is selected
		private static final String MOVE_SELECTION_TO = "moveSelectionTo";

		Actions(String name) {
			super(name);
		}

		public void actionPerformed(ActionEvent e) {
			String name = getName();
			JList list = (JList) e.getSource();
			BasicXListUI ui = (BasicXListUI) LookAndFeelUtils.getUIOfType(list.getUI(), BasicXListUI.class);

			if (name == SELECT_PREVIOUS_COLUMN) {
				changeSelection(list, CHANGE_SELECTION, getNextColumnIndex(list, ui, -1), -1);
			} else if (name == SELECT_PREVIOUS_COLUMN_EXTEND) {
				changeSelection(list, EXTEND_SELECTION, getNextColumnIndex(list, ui, -1), -1);
			} else if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD) {
				changeSelection(list, CHANGE_LEAD, getNextColumnIndex(list, ui, -1), -1);
			} else if (name == SELECT_NEXT_COLUMN) {
				changeSelection(list, CHANGE_SELECTION, getNextColumnIndex(list, ui, 1), 1);
			} else if (name == SELECT_NEXT_COLUMN_EXTEND) {
				changeSelection(list, EXTEND_SELECTION, getNextColumnIndex(list, ui, 1), 1);
			} else if (name == SELECT_NEXT_COLUMN_CHANGE_LEAD) {
				changeSelection(list, CHANGE_LEAD, getNextColumnIndex(list, ui, 1), 1);
			} else if (name == SELECT_PREVIOUS_ROW) {
				changeSelection(list, CHANGE_SELECTION, getNextIndex(list, ui, -1), -1);
			} else if (name == SELECT_PREVIOUS_ROW_EXTEND) {
				changeSelection(list, EXTEND_SELECTION, getNextIndex(list, ui, -1), -1);
			} else if (name == SELECT_PREVIOUS_ROW_CHANGE_LEAD) {
				changeSelection(list, CHANGE_LEAD, getNextIndex(list, ui, -1), -1);
			} else if (name == SELECT_NEXT_ROW) {
				changeSelection(list, CHANGE_SELECTION, getNextIndex(list, ui, 1), 1);
			} else if (name == SELECT_NEXT_ROW_EXTEND) {
				changeSelection(list, EXTEND_SELECTION, getNextIndex(list, ui, 1), 1);
			} else if (name == SELECT_NEXT_ROW_CHANGE_LEAD) {
				changeSelection(list, CHANGE_LEAD, getNextIndex(list, ui, 1), 1);
			} else if (name == SELECT_FIRST_ROW) {
				changeSelection(list, CHANGE_SELECTION, 0, -1);
			} else if (name == SELECT_FIRST_ROW_EXTEND) {
				changeSelection(list, EXTEND_SELECTION, 0, -1);
			} else if (name == SELECT_FIRST_ROW_CHANGE_LEAD) {
				changeSelection(list, CHANGE_LEAD, 0, -1);
			} else if (name == SELECT_LAST_ROW) {
				changeSelection(list, CHANGE_SELECTION, getElementCount(list) - 1, 1);
			} else if (name == SELECT_LAST_ROW_EXTEND) {
				changeSelection(list, EXTEND_SELECTION, getElementCount(list) - 1, 1);
			} else if (name == SELECT_LAST_ROW_CHANGE_LEAD) {
				changeSelection(list, CHANGE_LEAD, getElementCount(list) - 1, 1);
			} else if (name == SCROLL_UP) {
				changeSelection(list, CHANGE_SELECTION, getNextPageIndex(list, -1), -1);
			} else if (name == SCROLL_UP_EXTEND) {
				changeSelection(list, EXTEND_SELECTION, getNextPageIndex(list, -1), -1);
			} else if (name == SCROLL_UP_CHANGE_LEAD) {
				changeSelection(list, CHANGE_LEAD, getNextPageIndex(list, -1), -1);
			} else if (name == SCROLL_DOWN) {
				changeSelection(list, CHANGE_SELECTION, getNextPageIndex(list, 1), 1);
			} else if (name == SCROLL_DOWN_EXTEND) {
				changeSelection(list, EXTEND_SELECTION, getNextPageIndex(list, 1), 1);
			} else if (name == SCROLL_DOWN_CHANGE_LEAD) {
				changeSelection(list, CHANGE_LEAD, getNextPageIndex(list, 1), 1);
			} else if (name == SELECT_ALL) {
				selectAll(list);
			} else if (name == CLEAR_SELECTION) {
				clearSelection(list);
			} else if (name == ADD_TO_SELECTION) {
				int index = adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list);

				if (!list.isSelectedIndex(index)) {
					int oldAnchor = list.getSelectionModel().getAnchorSelectionIndex();
					list.setValueIsAdjusting(true);
					list.addSelectionInterval(index, index);
					list.getSelectionModel().setAnchorSelectionIndex(oldAnchor);
					list.setValueIsAdjusting(false);
				}
			} else if (name == TOGGLE_AND_ANCHOR) {
				int index = adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list);

				if (list.isSelectedIndex(index)) {
					list.removeSelectionInterval(index, index);
				} else {
					list.addSelectionInterval(index, index);
				}
			} else if (name == EXTEND_TO) {
				changeSelection(list, EXTEND_SELECTION, adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list), 0);
			} else if (name == MOVE_SELECTION_TO) {
				changeSelection(list, CHANGE_SELECTION, adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list), 0);
			}
		}

		/**
		 * @param list
		 * @return
		 */
		private int getElementCount(JList list) {
			return ((JXList) list).getElementCount();
		}

		public boolean isEnabled(Object c) {
			Object name = getName();
			if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD || name == SELECT_NEXT_COLUMN_CHANGE_LEAD
					|| name == SELECT_PREVIOUS_ROW_CHANGE_LEAD || name == SELECT_NEXT_ROW_CHANGE_LEAD
					|| name == SELECT_FIRST_ROW_CHANGE_LEAD || name == SELECT_LAST_ROW_CHANGE_LEAD
					|| name == SCROLL_UP_CHANGE_LEAD || name == SCROLL_DOWN_CHANGE_LEAD) {

				// discontinuous selection actions are only enabled for
				// DefaultListSelectionModel
				return c != null && ((JList) c).getSelectionModel() instanceof DefaultListSelectionModel;
			}

			return true;
		}

		private void clearSelection(JList list) {
			list.clearSelection();
		}

		private void selectAll(JList list) {
			int size = getElementCount(list);
			if (size > 0) {
				ListSelectionModel lsm = list.getSelectionModel();
				int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);

				if (lsm.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
					if (lead == -1) {
						int min = adjustIndex(list.getMinSelectionIndex(), list);
						lead = (min == -1 ? 0 : min);
					}

					list.setSelectionInterval(lead, lead);
					list.ensureIndexIsVisible(lead);
				} else {
					list.setValueIsAdjusting(true);

					int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);

					list.setSelectionInterval(0, size - 1);

					// this is done to restore the anchor and lead
					SwingXUtilities.setLeadAnchorWithoutSelection(lsm, anchor, lead);

					list.setValueIsAdjusting(false);
				}
			}
		}

		private int getNextPageIndex(JList list, int direction) {
			if (getElementCount(list) == 0) {
				return -1;
			}

			int index = -1;
			Rectangle visRect = list.getVisibleRect();
			ListSelectionModel lsm = list.getSelectionModel();
			int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
			Rectangle leadRect = (lead == -1) ? new Rectangle() : list.getCellBounds(lead, lead);

			if (list.getLayoutOrientation() == JList.VERTICAL_WRAP && list.getVisibleRowCount() <= 0) {
				if (!list.getComponentOrientation().isLeftToRight()) {
					direction = -direction;
				}
				// apply for horizontal scrolling: the step for next
				// page index is number of visible columns
				if (direction < 0) {
					// left
					visRect.x = leadRect.x + leadRect.width - visRect.width;
					Point p = new Point(visRect.x - 1, leadRect.y);
					index = list.locationToIndex(p);
					Rectangle cellBounds = list.getCellBounds(index, index);
					if (visRect.intersects(cellBounds)) {
						p.x = cellBounds.x - 1;
						index = list.locationToIndex(p);
						cellBounds = list.getCellBounds(index, index);
					}
					// this is necessary for right-to-left orientation only
					if (cellBounds.y != leadRect.y) {
						p.x = cellBounds.x + cellBounds.width;
						index = list.locationToIndex(p);
					}
				} else {
					// right
					visRect.x = leadRect.x;
					Point p = new Point(visRect.x + visRect.width, leadRect.y);
					index = list.locationToIndex(p);
					Rectangle cellBounds = list.getCellBounds(index, index);
					if (visRect.intersects(cellBounds)) {
						p.x = cellBounds.x + cellBounds.width;
						index = list.locationToIndex(p);
						cellBounds = list.getCellBounds(index, index);
					}
					if (cellBounds.y != leadRect.y) {
						p.x = cellBounds.x - 1;
						index = list.locationToIndex(p);
					}
				}
			} else {
				if (direction < 0) {
					// up
					// go to the first visible cell
					Point p = new Point(leadRect.x, visRect.y);
					index = list.locationToIndex(p);
					if (lead <= index) {
						// if lead is the first visible cell (or above it)
						// adjust the visible rect up
						visRect.y = leadRect.y + leadRect.height - visRect.height;
						p.y = visRect.y;
						index = list.locationToIndex(p);
						Rectangle cellBounds = list.getCellBounds(index, index);
						// go one cell down if first visible cell doesn't fit
						// into adjasted visible rectangle
						if (cellBounds.y < visRect.y) {
							p.y = cellBounds.y + cellBounds.height;
							index = list.locationToIndex(p);
							cellBounds = list.getCellBounds(index, index);
						}
						// if index isn't less then lead
						// try to go to cell previous to lead
						if (cellBounds.y >= leadRect.y) {
							p.y = leadRect.y - 1;
							index = list.locationToIndex(p);
						}
					}
				} else {
					// down
					// go to the last completely visible cell
					Point p = new Point(leadRect.x, visRect.y + visRect.height - 1);
					index = list.locationToIndex(p);
					Rectangle cellBounds = list.getCellBounds(index, index);
					// go up one cell if last visible cell doesn't fit
					// into visible rectangle
					if (cellBounds.y + cellBounds.height > visRect.y + visRect.height) {
						p.y = cellBounds.y - 1;
						index = list.locationToIndex(p);
						cellBounds = list.getCellBounds(index, index);
						index = Math.max(index, lead);
					}

					if (lead >= index) {
						// if lead is the last completely visible index
						// (or below it) adjust the visible rect down
						visRect.y = leadRect.y;
						p.y = visRect.y + visRect.height - 1;
						index = list.locationToIndex(p);
						cellBounds = list.getCellBounds(index, index);
						// go one cell up if last visible cell doesn't fit
						// into adjasted visible rectangle
						if (cellBounds.y + cellBounds.height > visRect.y + visRect.height) {
							p.y = cellBounds.y - 1;
							index = list.locationToIndex(p);
							cellBounds = list.getCellBounds(index, index);
						}
						// if index isn't greater then lead
						// try to go to cell next after lead
						if (cellBounds.y <= leadRect.y) {
							p.y = leadRect.y + leadRect.height;
							index = list.locationToIndex(p);
						}
					}
				}
			}
			return index;
		}

		private void changeSelection(JList list, int type, int index, int direction) {
			if (index >= 0 && index < getElementCount(list)) {
				ListSelectionModel lsm = list.getSelectionModel();

				// CHANGE_LEAD is only valid with multiple interval selection
				if (type == CHANGE_LEAD && list.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {

					type = CHANGE_SELECTION;
				}

				// IMPORTANT - This needs to happen before the index is changed.
				// This is because JFileChooser, which uses JList, also scrolls
				// the selected item into view. If that happens first, then
				// this method becomes a no-op.
				adjustScrollPositionIfNecessary(list, index, direction);

				if (type == EXTEND_SELECTION) {
					int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
					if (anchor == -1) {
						anchor = 0;
					}

					list.setSelectionInterval(anchor, index);
				} else if (type == CHANGE_SELECTION) {
					list.setSelectedIndex(index);
				} else {
					// casting should be safe since the action is only enabled
					// for DefaultListSelectionModel
					if (lsm instanceof DefaultListSelectionModel)
						((DefaultListSelectionModel) lsm).moveLeadSelectionIndex(index);
				}
			}
		}

		/**
		 * When scroll down makes selected index the last completely visible
		 * index. When scroll up makes selected index the first visible index.
		 * Adjust visible rectangle respect to list's component orientation.
		 */
		private void adjustScrollPositionIfNecessary(JList list, int index, int direction) {
			if (direction == 0) {
				return;
			}
			Rectangle cellBounds = list.getCellBounds(index, index);
			Rectangle visRect = list.getVisibleRect();
			if (cellBounds != null && !visRect.contains(cellBounds)) {
				if (list.getLayoutOrientation() == JList.VERTICAL_WRAP && list.getVisibleRowCount() <= 0) {
					// horizontal
					if (list.getComponentOrientation().isLeftToRight()) {
						if (direction > 0) {
							// right for left-to-right
							int x = Math.max(0, cellBounds.x + cellBounds.width - visRect.width);
							int startIndex = list.locationToIndex(new Point(x, cellBounds.y));
							Rectangle startRect = list.getCellBounds(startIndex, startIndex);
							if (startRect.x < x && startRect.x < cellBounds.x) {
								startRect.x += startRect.width;
								startIndex = list.locationToIndex(startRect.getLocation());
								startRect = list.getCellBounds(startIndex, startIndex);
							}
							cellBounds = startRect;
						}
						cellBounds.width = visRect.width;
					} else {
						if (direction > 0) {
							// left for right-to-left
							int x = cellBounds.x + visRect.width;
							int rightIndex = list.locationToIndex(new Point(x, cellBounds.y));
							Rectangle rightRect = list.getCellBounds(rightIndex, rightIndex);
							if (rightRect.x + rightRect.width > x && rightRect.x > cellBounds.x) {
								rightRect.width = 0;
							}
							cellBounds.x = Math.max(0, rightRect.x + rightRect.width - visRect.width);
							cellBounds.width = visRect.width;
						} else {
							cellBounds.x += Math.max(0, cellBounds.width - visRect.width);
							// adjust width to fit into visible rectangle
							cellBounds.width = Math.min(cellBounds.width, visRect.width);
						}
					}
				} else {
					// vertical
					if (direction > 0
							&& (cellBounds.y < visRect.y || cellBounds.y + cellBounds.height > visRect.y + visRect.height)) {
						// down
						int y = Math.max(0, cellBounds.y + cellBounds.height - visRect.height);
						int startIndex = list.locationToIndex(new Point(cellBounds.x, y));
						Rectangle startRect = list.getCellBounds(startIndex, startIndex);
						if (startRect.y < y && startRect.y < cellBounds.y) {
							startRect.y += startRect.height;
							startIndex = list.locationToIndex(startRect.getLocation());
							startRect = list.getCellBounds(startIndex, startIndex);
						}
						cellBounds = startRect;
						cellBounds.height = visRect.height;
					} else {
						// adjust height to fit into visible rectangle
						cellBounds.height = Math.min(cellBounds.height, visRect.height);
					}
				}
				list.scrollRectToVisible(cellBounds);
			}
		}

		private int getNextColumnIndex(JList list, BasicXListUI ui, int amount) {
			if (list.getLayoutOrientation() != JList.VERTICAL) {
				int index = adjustIndex(list.getLeadSelectionIndex(), list);
				int size = getElementCount(list);

				if (index == -1) {
					return 0;
				} else if (size == 1) {
					// there's only one item so we should select it
					return 0;
				} else if (ui == null || ui.columnCount <= 1) {
					return -1;
				}

				int column = ui.convertModelToColumn(index);
				int row = ui.convertModelToRow(index);

				column += amount;
				if (column >= ui.columnCount || column < 0) {
					// No wrapping.
					return -1;
				}
				int maxRowCount = ui.getRowCount(column);
				if (row >= maxRowCount) {
					return -1;
				}
				return ui.getModelIndex(column, row);
			}
			// Won't change the selection.
			return -1;
		}

		private int getNextIndex(JList list, BasicXListUI ui, int amount) {
			int index = adjustIndex(list.getLeadSelectionIndex(), list);
			int size = getElementCount(list);

			if (index == -1) {
				if (size > 0) {
					if (amount > 0) {
						index = 0;
					} else {
						index = size - 1;
					}
				}
			} else if (size == 1) {
				// there's only one item so we should select it
				index = 0;
			} else if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP) {
				if (ui != null) {
					index += ui.columnCount * amount;
				}
			} else {
				index += amount;
			}

			return index;
		}
	}

	private class Handler implements FocusListener, KeyListener, ListDataListener, ListSelectionListener, MouseInputListener,
			PropertyChangeListener, BeforeDrag {
		//
		// KeyListener
		//
		private String prefix = "";
		private String typedString = "";
		private long lastTime = 0L;

		/**
		 * Invoked when a key has been typed.
		 * 
		 * Moves the keyboard focus to the first element whose prefix matches
		 * the sequence of alphanumeric keys pressed by the user with delay less
		 * than value of <code>timeFactor</code> property (or 1000 milliseconds
		 * if it is not defined). Subsequent same key presses move the keyboard
		 * focus to the next object that starts with the same letter until
		 * another key is pressed, then it is treated as the prefix with
		 * appropriate number of the same letters followed by first typed anothe
		 * letter.
		 */
		public void keyTyped(KeyEvent e) {
			JList src = (JList) e.getSource();

			if (getElementCount() == 0 || e.isAltDown() || e.isControlDown() || e.isMetaDown() || isNavigationKey(e)) {
				// Nothing to select
				return;
			}
			boolean startingFromSelection = true;

			char c = e.getKeyChar();

			long time = e.getWhen();
			int startIndex = adjustIndex(src.getLeadSelectionIndex(), list);
			if (time - lastTime < timeFactor) {
				typedString += c;
				if ((prefix.length() == 1) && (c == prefix.charAt(0))) {
					// Subsequent same key presses move the keyboard focus to
					// the next
					// object that starts with the same letter.
					startIndex++;
				} else {
					prefix = typedString;
				}
			} else {
				startIndex++;
				typedString = "" + c;
				prefix = typedString;
			}
			lastTime = time;

			if (startIndex < 0 || startIndex >= getElementCount()) {
				startingFromSelection = false;
				startIndex = 0;
			}
			int index = src.getNextMatch(prefix, startIndex, Position.Bias.Forward);
			if (index >= 0) {
				src.setSelectedIndex(index);
				src.ensureIndexIsVisible(index);
			} else if (startingFromSelection) { // wrap
				index = src.getNextMatch(prefix, 0, Position.Bias.Forward);
				if (index >= 0) {
					src.setSelectedIndex(index);
					src.ensureIndexIsVisible(index);
				}
			}
		}

		/**
		 * Invoked when a key has been pressed.
		 *
		 * Checks to see if the key event is a navigation key to prevent
		 * dispatching these keys for the first letter navigation.
		 */
		public void keyPressed(KeyEvent e) {
			if (isNavigationKey(e)) {
				prefix = "";
				typedString = "";
				lastTime = 0L;
			}
		}

		/**
		 * Invoked when a key has been released. See the class description for
		 * {@link KeyEvent} for a definition of a key released event.
		 */
		public void keyReleased(KeyEvent e) {
		}

		/**
		 * Returns whether or not the supplied key event maps to a key that is
		 * used for navigation. This is used for optimizing key input by only
		 * passing non- navigation keys to the first letter navigation
		 * mechanism.
		 */
		private boolean isNavigationKey(KeyEvent event) {
			InputMap inputMap = list.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
			KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);

			if (inputMap != null && inputMap.get(key) != null) {
				return true;
			}
			return false;
		}

		//
		// PropertyChangeListener
		//
		public void propertyChange(PropertyChangeEvent e) {
			String propertyName = e.getPropertyName();

			/*
			 * If the JList.model property changes, remove our listener,
			 * listDataListener from the old model and add it to the new one.
			 */
			if (propertyName == "model") {
				ListModel oldModel = (ListModel) e.getOldValue();
				ListModel newModel = (ListModel) e.getNewValue();
				if (oldModel != null) {
					oldModel.removeListDataListener(listDataListener);
				}
				if (newModel != null) {
					newModel.addListDataListener(listDataListener);
				}
				updateLayoutStateNeeded |= modelChanged;
				redrawList();
			}

			/*
			 * If the JList.selectionModel property changes, remove our
			 * listener, listSelectionListener from the old selectionModel and
			 * add it to the new one.
			 */
			else if (propertyName == "selectionModel") {
				ListSelectionModel oldModel = (ListSelectionModel) e.getOldValue();
				ListSelectionModel newModel = (ListSelectionModel) e.getNewValue();
				if (oldModel != null) {
					oldModel.removeListSelectionListener(listSelectionListener);
				}
				if (newModel != null) {
					newModel.addListSelectionListener(listSelectionListener);
				}
				updateLayoutStateNeeded |= modelChanged;
				redrawList();
			} else if (propertyName == "cellRenderer") {
				updateLayoutStateNeeded |= cellRendererChanged;
				redrawList();
			} else if (propertyName == "font") {
				updateLayoutStateNeeded |= fontChanged;
				redrawList();
			} else if (propertyName == "prototypeCellValue") {
				updateLayoutStateNeeded |= prototypeCellValueChanged;
				redrawList();
			} else if (propertyName == "fixedCellHeight") {
				updateLayoutStateNeeded |= fixedCellHeightChanged;
				redrawList();
			} else if (propertyName == "fixedCellWidth") {
				updateLayoutStateNeeded |= fixedCellWidthChanged;
				redrawList();
			} else if (propertyName == "cellRenderer") {
				updateLayoutStateNeeded |= cellRendererChanged;
				redrawList();
			} else if (propertyName == "selectionForeground") {
				list.repaint();
			} else if (propertyName == "selectionBackground") {
				list.repaint();
			} else if ("layoutOrientation" == propertyName) {
				updateLayoutStateNeeded |= layoutOrientationChanged;
				layoutOrientation = list.getLayoutOrientation();
				redrawList();
			} else if ("visibleRowCount" == propertyName) {
				if (layoutOrientation != JList.VERTICAL) {
					updateLayoutStateNeeded |= layoutOrientationChanged;
					redrawList();
				}
			} else if ("componentOrientation" == propertyName) {
				isLeftToRight = list.getComponentOrientation().isLeftToRight();
				updateLayoutStateNeeded |= componentOrientationChanged;
				redrawList();

				InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
				SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, inputMap);
			} else if ("List.isFileList" == propertyName) {
				updateIsFileList();
				redrawList();
			} else if ("dropLocation" == propertyName) {
				JList.DropLocation oldValue = (JList.DropLocation) e.getOldValue();
				repaintDropLocation(oldValue);
				repaintDropLocation(list.getDropLocation());
			}
		}

		private void repaintDropLocation(JList.DropLocation loc) {
			if (loc == null) {
				return;
			}

			Rectangle r;

			if (loc.isInsert()) {
				r = getDropLineRect(loc);
			} else {
				r = getCellBounds(list, loc.getIndex());
			}

			if (r != null) {
				list.repaint(r);
			}
		}

		//
		// ListDataListener
		//
		public void intervalAdded(ListDataEvent e) {
			updateLayoutStateNeeded = modelChanged;

			int minIndex = Math.min(e.getIndex0(), e.getIndex1());
			int maxIndex = Math.max(e.getIndex0(), e.getIndex1());

			/*
			 * Sync the SelectionModel with the DataModel.
			 */

			ListSelectionModel sm = list.getSelectionModel();
			if (sm != null) {
				sm.insertIndexInterval(minIndex, maxIndex - minIndex + 1, true);
			}

			/*
			 * Repaint the entire list, from the origin of the first added cell,
			 * to the bottom of the component.
			 */
			redrawList();
		}

		public void intervalRemoved(ListDataEvent e) {
			updateLayoutStateNeeded = modelChanged;

			/*
			 * Sync the SelectionModel with the DataModel.
			 */

			ListSelectionModel sm = list.getSelectionModel();
			if (sm != null) {
				sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
			}

			/*
			 * Repaint the entire list, from the origin of the first removed
			 * cell, to the bottom of the component.
			 */

			redrawList();
		}

		public void contentsChanged(ListDataEvent e) {
			updateLayoutStateNeeded = modelChanged;
			redrawList();
		}

		//
		// ListSelectionListener
		//
		public void valueChanged(ListSelectionEvent e) {
			maybeUpdateLayoutState();
			int size = getElementCount();
			int firstIndex = Math.min(size - 1, Math.max(e.getFirstIndex(), 0));
			int lastIndex = Math.min(size - 1, Math.max(e.getLastIndex(), 0));

			Rectangle bounds = getCellBounds(list, firstIndex, lastIndex);

			if (bounds != null) {
				list.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
			}
		}

		//
		// MouseListener
		//
		public void mouseClicked(MouseEvent e) {
		}

		public void mouseEntered(MouseEvent e) {
		}

		public void mouseExited(MouseEvent e) {
		}

		// Whether or not the mouse press (which is being considered as part
		// of a drag sequence) also caused the selection change to be fully
		// processed.
		private boolean dragPressDidSelection;

		public void mousePressed(MouseEvent e) {
			if (SwingXUtilities.shouldIgnore(e, list)) {
				return;
			}

			boolean dragEnabled = list.getDragEnabled();
			boolean grabFocus = true;

			// different behavior if drag is enabled
			if (dragEnabled) {
				// PENDING JW: this isn't aware of sorting/filtering - fix!
				int row = SwingXUtilities.loc2IndexFileList(list, e.getPoint());
				// if we have a valid row and this is a drag initiating event
				if (row != -1 && DragRecognitionSupport.mousePressed(e)) {
					dragPressDidSelection = false;

					if (e.isControlDown()) {
						// do nothing for control - will be handled on release
						// or when drag starts
						return;
					} else if (!e.isShiftDown() && list.isSelectedIndex(row)) {
						// clicking on something that's already selected
						// and need to make it the lead now
						list.addSelectionInterval(row, row);
						return;
					}

					// could be a drag initiating event - don't grab focus
					grabFocus = false;

					dragPressDidSelection = true;
				}
			} else {
				// When drag is enabled mouse drags won't change the selection
				// in the list, so we only set the isAdjusting flag when it's
				// not enabled
				list.setValueIsAdjusting(true);
			}

			if (grabFocus) {
				SwingXUtilities.adjustFocus(list);
			}

			adjustSelection(e);
		}

		private void adjustSelection(MouseEvent e) {
			// PENDING JW: this isn't aware of sorting/filtering - fix!
			int row = SwingXUtilities.loc2IndexFileList(list, e.getPoint());
			if (row < 0) {
				// If shift is down in multi-select, we should do nothing.
				// For single select or non-shift-click, clear the selection
				if (isFileList && e.getID() == MouseEvent.MOUSE_PRESSED
						&& (!e.isShiftDown() || list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)) {
					list.clearSelection();
				}
			} else {
				int anchorIndex = adjustIndex(list.getAnchorSelectionIndex(), list);
				boolean anchorSelected;
				if (anchorIndex == -1) {
					anchorIndex = 0;
					anchorSelected = false;
				} else {
					anchorSelected = list.isSelectedIndex(anchorIndex);
				}

				if (e.isControlDown()) {
					if (e.isShiftDown()) {
						if (anchorSelected) {
							list.addSelectionInterval(anchorIndex, row);
						} else {
							list.removeSelectionInterval(anchorIndex, row);
							if (isFileList) {
								list.addSelectionInterval(row, row);
								list.getSelectionModel().setAnchorSelectionIndex(anchorIndex);
							}
						}
					} else if (list.isSelectedIndex(row)) {
						list.removeSelectionInterval(row, row);
					} else {
						list.addSelectionInterval(row, row);
					}
				} else if (e.isShiftDown()) {
					list.setSelectionInterval(anchorIndex, row);
				} else {
					list.setSelectionInterval(row, row);
				}
			}
		}

		public void dragStarting(MouseEvent me) {
			if (me.isControlDown()) {
				// PENDING JW: this isn't aware of sorting/filtering - fix!
				int row = SwingXUtilities.loc2IndexFileList(list, me.getPoint());
				list.addSelectionInterval(row, row);
			}
		}

		public void mouseDragged(MouseEvent e) {
			if (SwingXUtilities.shouldIgnore(e, list)) {
				return;
			}

			if (list.getDragEnabled()) {
				DragRecognitionSupport.mouseDragged(e, this);
				return;
			}

			if (e.isShiftDown() || e.isControlDown()) {
				return;
			}

			int row = locationToIndex(list, e.getPoint());
			if (row != -1) {
				// 4835633. Dragging onto a File should not select it.
				if (isFileList) {
					return;
				}
				Rectangle cellBounds = getCellBounds(list, row, row);
				if (cellBounds != null) {
					list.scrollRectToVisible(cellBounds);
					list.setSelectionInterval(row, row);
				}
			}
		}

		public void mouseMoved(MouseEvent e) {
		}

		public void mouseReleased(MouseEvent e) {
			if (SwingXUtilities.shouldIgnore(e, list)) {
				return;
			}

			if (list.getDragEnabled()) {
				MouseEvent me = DragRecognitionSupport.mouseReleased(e);
				if (me != null) {
					SwingXUtilities.adjustFocus(list);
					if (!dragPressDidSelection) {
						adjustSelection(me);
					}
				}
			} else {
				list.setValueIsAdjusting(false);
			}
		}

		//
		// FocusListener
		//
		protected void repaintCellFocus() {
			int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
			if (leadIndex != -1) {
				Rectangle r = getCellBounds(list, leadIndex, leadIndex);
				if (r != null) {
					list.repaint(r.x, r.y, r.width, r.height);
				}
			}
		}

		/*
		 * The focusGained() focusLost() methods run when the JList focus
		 * changes.
		 */

		public void focusGained(FocusEvent e) {
			repaintCellFocus();
		}

		public void focusLost(FocusEvent e) {
			repaintCellFocus();
		}
	}

	private static int adjustIndex(int index, JList list) {
		return index < ((JXList) list).getElementCount() ? index : -1;
	}

	private static final TransferHandler defaultTransferHandler = new ListTransferHandler();

	static class ListTransferHandler extends TransferHandler implements UIResource {

		/**
		 * Create a Transferable to use as the source for a data transfer.
		 *
		 * @param c
		 *            The component holding the data to be transfered. This
		 *            argument is provided to enable sharing of TransferHandlers
		 *            by multiple components.
		 * @return The representation of the data to be transfered.
		 * 
		 */
		protected Transferable createTransferable(JComponent c) {
			if (c instanceof JList) {
				JList list = (JList) c;
				Object[] values = list.getSelectedValues();

				if (values == null || values.length == 0) {
					return null;
				}

				StringBuffer plainBuf = new StringBuffer();
				StringBuffer htmlBuf = new StringBuffer();

				htmlBuf.append("<html>\n<body>\n<ul>\n");

				for (int i = 0; i < values.length; i++) {
					Object obj = values[i];
					String val = ((obj == null) ? "" : obj.toString());
					plainBuf.append(val + "\n");
					htmlBuf.append("  <li>" + val + "\n");
				}

				// remove the last newline
				plainBuf.deleteCharAt(plainBuf.length() - 1);
				htmlBuf.append("</ul>\n</body>\n</html>");

				return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
			}

			return null;
		}

		public int getSourceActions(JComponent c) {
			return COPY;
		}

	}
}
